package org.zoomdev.zoom.dao.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.Entity;
import org.zoomdev.zoom.dao.EntityField;
import org.zoomdev.zoom.dao.adapters.RecordAdapter;
import org.zoomdev.zoom.dao.adapters.StatementAdapter;
import org.zoomdev.zoom.dao.modules.CoreDaoModule;
import org.zoomdev.zoom.dao.utils.DaoUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public final class EntityActiveRecordUtils {
    private static final Logger log = LoggerFactory.getLogger(CoreDaoModule.NAME);

    public static int executeInsert(
            Connection connection,
            final Entity entity,
            final Object data,
            final SimpleSqlBuilder builder, boolean output) throws SQLException {
        PreparedStatement ps = null;
        try {
            ps = entity.prepareInsert(connection, builder.sql.toString());
            prepareStatement(ps, builder, output);
            int ret = ps.executeUpdate();
            if (ret > 0) {
                entity.afterInsert(data, ps);
            }
            return ret;
        } finally {
            DaoUtils.close(ps);
        }

    }

    static PreparedStatement prepareStatement(PreparedStatement ps,
                                              SimpleSqlBuilder builder,
                                              boolean output) throws SQLException {

        if (output) {
            log.info(builder.printSql());
        }
        List<Object> values = builder.values;
        List<StatementAdapter> adapters = builder.adapters;
        for (int index = 0, c = values.size(), m = adapters.size(); index < c; ++index) {
            if (index >= m) {
                ps.setObject(index + 1, values.get(index));
            } else {
                StatementAdapter adapter = adapters.get(index);
                adapter.adapt(ps, index + 1, values.get(index));
            }

        }
        return ps;
    }

    public static int executeUpdate(
            final Connection connection,
            final SimpleSqlBuilder builder,
            boolean output) throws SQLException {
        PreparedStatement ps = null;
        try {
            ps = prepareStatement(connection, builder, output);
            return ps.executeUpdate();
        } finally {
            DaoUtils.close(ps);
        }
    }

    static PreparedStatement prepareStatement(
            Connection connection,
            SimpleSqlBuilder builder,
            boolean output) throws SQLException {

        String sql = builder.sql.toString();
        PreparedStatement ps = connection.prepareStatement(sql);

        return prepareStatement(ps, builder, output);
    }

    static <T> List<T> executeQuery(
            Connection connection,
            String sql,
            List<Object> values,
            Entity<?> entity,
            boolean output) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = StatementUtils.prepareStatement(
                    connection,
                    sql,
                    values,
                    output);
            rs = ps.executeQuery();
            return buildList2(rs, entity, sql);
        } finally {
            DaoUtils.close(rs);
            DaoUtils.close(ps);
        }
    }

    static <T> List<T> buildList2(
            ResultSet rs,
            Entity entity,
            String sql
    ) throws SQLException {
//        ResultSetMetaData metaData= rs.getMetaData();
//        for(int i=0,c=metaData.getColumnCount();i<c;++i){
//            String name = metaData.getColumnLabel(i);
//
//        }
        List<T> list = new ArrayList<>();
        while (rs.next()) {
            T data = buildRecord2(rs, entity);
            list.add(data);
        }

        return list;
    }

    static <T> T buildRecord2(
            ResultSet rs,
            Entity entity
    ) throws SQLException {
        Object data = entity.newInstance();
        EntityField[] entityFields = entity.getEntityFields();
        for (int i = 0, c = entityFields.length; i < c; ++i) {
            EntityField entityField = entityFields[i];
            assert (entityField != null);
            try {
                Object r = rs.getObject(entityField.getColumnName());
                entityField.set(data, entityField.getFieldValue(r));
            } catch (Exception e) {
                throw new DaoException("不能设置查询结果" + entityField.getFieldName(), e);
            }

        }
        return (T) data;
    }

    static Object[] executeGetValues(
            Connection connection,
            final SimpleSqlBuilder builder,
            int count,
            boolean output) throws SQLException {

        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = StatementUtils.prepareStatement(connection,
                    builder.sql.toString(),
                    builder.values, output);
            rs = ps.executeQuery();
            if (rs.next()) {
                Object[] ret = new Object[count];
                for (int i = 0; i < count; ++i) {
                    Object r = rs.getObject(i + 1);
                    ret[i] = r;
                }
                return ret;
            }
            return null;
        } finally {
            DaoUtils.close(rs);
            DaoUtils.close(ps);
        }


    }

    static <T> T executeGet(
            Connection connection,
            SimpleSqlBuilder builder,
            Entity<T> entity,
            List<EntityField> entityFields,
            RecordAdapter<T> adapter,
            boolean output) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = StatementUtils.prepareStatement(connection, builder.sql.toString(), builder.values, output);
            rs = ps.executeQuery();
            if (rs.next()) {
                return buildRecord(rs, entity, entityFields, adapter);
            }
            return null;
        } finally {
            DaoUtils.close(rs);
            DaoUtils.close(ps);
        }
    }

    static <T> T buildRecord(
            ResultSet rs,
            Entity<T> entity,
            List<EntityField> entityFields,
            RecordAdapter<T> adapter
    ) {
        T data = entity.newInstance();
        for (int i = 0, c = entityFields.size(); i < c; ++i) {
            EntityField entityField = entityFields.get(i);
            assert (entityField != null);
            try {
                Object r = rs.getObject(i + 1);
                Object value = entityField.getFieldValue(r);
                entityField.set(data, value);
            } catch (Exception e) {
                throw new DaoException("不能设置查询结果" + entityField.getFieldName() +
                        " table:" + entity.getTable() + " type:" + entity.getType().getName(), e);
            }
        }

        if (adapter != null) {
            adapter.adapt(data, rs);
        }

        return data;
    }

    static <T> List<T> executeQuery(
            Connection connection,
            SimpleSqlBuilder builder,
            List<EntityField> entityFields,
            Entity<T> entity,
            RecordAdapter<T> adapter,
            boolean output) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = StatementUtils.prepareStatement(
                    connection,
                    builder.sql.toString(),
                    builder.values,
                    output);
            rs = ps.executeQuery();
            return buildList(rs, entity, entityFields, adapter);
        } finally {
            DaoUtils.close(rs);
            DaoUtils.close(ps);
        }
    }

    static <T> List<T> buildList(
            ResultSet rs,
            Entity<T> entity,
            List<EntityField> entityFields,
            RecordAdapter<T> adapter
    ) throws SQLException {

        List<T> list = new ArrayList<T>();
        while (rs.next()) {
            T data = buildRecord(rs, entity, entityFields, adapter);
            list.add(data);
        }

        return list;

    }
}
